package gotnames.dm;

import gotnames.Utils;

import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.medallia.tiny.CollUtils;

/**
 * Utility class used to find an existing object or, or not found, create a new
 * and populate it with a set of values. See {@link #go()}. 
 */
public abstract class FindOrCreate<X> {
	
	private final PersistenceManager pm;
	private final Class<X> clazz;

	/**
	 * @param clazz {@link Class#newInstance()} is used to create the new object if needed
	 */
	public FindOrCreate(PersistenceManager pm, Class<X> clazz) {
		this.pm = pm;
		this.clazz = clazz;
	}

	/**
	 * @return the return value from {@link Query#execute()} (or any of the other
	 *         execute methods) which must be contain exactly zero or one object
	 */
	protected abstract Object execute(Query query);

	/** Populate values on the given object */
	protected abstract void setValues(X x);

	/** Hook which by default just calls {@link PersistenceManager#makePersistent(Object)} */
	protected void makePersistent(X x) {
		pm.makePersistent(x);
	}

	/** Calls {@link #go()} inside a {@link KTrans} */
	public PersistedEntityValue<X> trans() {
		return new KTrans<PersistedEntityValue<X>>(pm) {
			@Override protected PersistedEntityValue<X> call() {
				return FindOrCreate.this.go();
			}
		}.go();
	}
	
	/** @return a {@link PersistedEntityValue} with either the found object or a newly crated */
	public PersistedEntityValue<X> go() {
		Query query = pm.newQuery(clazz);
		
		try {
			List<X> results = Utils.<List<X>>cast(execute(query));
			if (!results.isEmpty())
				return PersistedEntityValue.found(CollUtils.getOnlyElement(results));
		} finally {
			query.closeAll();
		}
		
		// Not found; construct a new object
		X x;
		try {
			x = clazz.newInstance();
		} catch (InstantiationException e) {
			throw new IllegalStateException(e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		}
		setValues(x);
		
		makePersistent(x);
		return PersistedEntityValue.created(x);
	}

}
